package guardian

import (
	"github.com/Unixeno/islands/conf"
	"github.com/Unixeno/islands/island"
	"github.com/Unixeno/islands/lode"
	"github.com/Unixeno/islands/pb"
	"github.com/golang/protobuf/ptypes/empty"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"net"
	"os"
	"os/user"
	"regexp"
	"strconv"
	"syscall"
)

const apiPattern = "(tcp|unix)://(.+)"

var rpcServer = grpc.NewServer()

type server struct{}

func StartServer() {
	api := conf.GetString("APIPath")
	reg := regexp.MustCompile(apiPattern)
	r := reg.FindStringSubmatch(api)
	if r == nil || len(r) != 3 {
		logger.WithField("APIPath", api).Fatal("invalid APIPath")
	}

	//检查unix socket 是否占用, 占用则删除
	if r[1] == "unix" {
		if s, err := os.Stat(r[2]); err == nil {
			// 如果对应路径是个文件夹则报错
			if s.IsDir() {
				logger.WithField("APIPath", api).Fatal("invalid APIPath, target is a directory")
			} else {
				if err := os.Remove(r[2]); err != nil {
					logger.WithError(err).Fatal("failed to bind the domain socket")
				}
			}
		}
	}

	s, err := net.Listen(r[1], r[2])
	if err != nil {
		logger.WithField("APIPath", api).WithError(err).Fatal("failed to bind the domain socket")
	}

	if r[1] == "unix" {
		if group, err := user.LookupGroup("islands"); err == nil {
			gid, _ := strconv.Atoi(group.Gid)
			syscall.Chown(r[2], syscall.Getuid(), gid)
			syscall.Chmod(r[2], 0770)
		} else {
			logger.WithError(err).Error("failed to get current groups")
		}
	}

	pb.RegisterIslandsServer(rpcServer, &server{})
	reflection.Register(rpcServer)
	logger.WithField("APIPath", api).Info("rpc server started!")
	if err := rpcServer.Serve(s); err != nil {
		logger.Fatalf("failed to serve: %v", err)
	}
}

func (s *server) ListImage(ctx context.Context, _ *empty.Empty) (*pb.ListImageResp, error) {
	logger.Debug("list local image")
	images := make([]*pb.Image, 0)
	for _, image := range lode.List() {
		images = append(images, &pb.Image{
			Id:         image.GetID(),
			CreateTime: image.GetCreateTime().String(),
			Name:       image.GetName(),
			Containers: int32(image.GetCounter()),
		})
	}
	return &pb.ListImageResp{
		Images: images,
	}, nil
}

func (s *server) ImportDockerImage(ctx context.Context, req *pb.ImportDockerImageReq) (*pb.ImportImageResp, error) {
	logger.WithField("path", req.Path).Debug("import docker image")
	if id, err := lode.ImportDockerImage(req.Path, req.Name); err != nil {
		return &pb.ImportImageResp{Err: &pb.Error{Code: 1, Message: err.Error()}}, nil
	} else {
		return &pb.ImportImageResp{Id: id}, nil
	}
}

func (s *server) ImportImageWithNeed(ctx context.Context, req *pb.ImportImageWithNeedReq) (*pb.ImportImageWithNeedResp, error) {
	logger.WithField("path", req.ConfigFilePath).Debug("import image with manifest")

	layerInfos := pbLayersToLayerInfos(req.Layers)
	if layers, id, err := lode.ImportImageWithNeed(req.ImageName, req.ConfigFilePath, layerInfos); err != nil {
		return &pb.ImportImageWithNeedResp{
			Imported: false,
			Layers:   nil,
			Id:       id,
		}, err
	} else {

		if len(layers) == 0 {
			return &pb.ImportImageWithNeedResp{
				Imported: true,
				Layers:   nil,
				Id:       id,
			}, nil
		}
		pbLayers := layerInfosToPbLayers(layers)

		return &pb.ImportImageWithNeedResp{
			Imported: false,
			Layers:   pbLayers,
			Id:       id,
		}, nil

	}
}

func (s *server) ImportLayers(ctx context.Context, req *pb.ImportLayersReq) (*pb.Error, error) {
	logger.WithField("path", req.LayersDir).Debug("import layers")
	layers := pbLayersToLayerInfos(req.Layers)
	if err := lode.ImportLayers(req.LayersDir, layers); err != nil {
		return newErr(1, err), nil
	} else {
		return &pb.Error{}, nil
	}
}

func (s *server) ImportRawImage(ctx context.Context, req *pb.ImportRawImageReq) (*pb.ImportImageResp, error) {
	logger.WithField("path", req.Path).Debug("import raw image")
	if id, err := lode.ImportRawImage(req.Path, req.Name); err != nil {
		return &pb.ImportImageResp{Err: &pb.Error{Code: 2, Message: err.Error()}}, nil
	} else {
		return &pb.ImportImageResp{Id: id}, nil
	}
}

func (s *server) DeleteImage(ctx context.Context, req *pb.DeleteImageReq) (*pb.Error, error) {
	logger.WithField("id", req.Id).Debug("request to delete local image")
	if err := lode.RemoveImage(req.Id); err != nil {
		return newErr(6, err), err
	} else {
		return &pb.Error{}, nil
	}
}

func IslandExitHandle(exit chan os.Signal) {
	signal := <-exit

	logger.WithField("signal", signal).Info("Clean up the islands")
	CleanUp()
}

func CleanUp() {
	// 关闭grpcserver
	logger.Info("shutdown rpc server")
	rpcServer.Stop()

	logger.Info("receive signal, stop containers")
	island.IslandCleanUp()

	logger.Info("receive signal, clean up the images")
	// Todo: 停止lode
}

func (s *server) ExportImage(ctx context.Context, req *pb.ExportImageReq) (*pb.Error, error) {
	logger.WithField("id", req.Id).Debug("request to export local image to ", req.TarFile)
	image, err := lode.OpenImage(req.Id)
	if err != nil {
		return newErr(6, err), err
	} else {
		if err := image.Export(req.TarFile); err != nil {
			return newErr(6, err), err
		} else {
			return &pb.Error{}, nil
		}
	}
}
